#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include <GLES/gl.h>
#include <GLES/glext.h>

#include <QCAR/QCAR.h>
#include <QCAR/CameraDevice.h>
#include <QCAR/Renderer.h>
#include <QCAR/VideoBackgroundConfig.h>
#include <QCAR/Trackable.h>
#include <QCAR/Tool.h>
#include <QCAR/Tracker.h>
#include <QCAR/CameraCalibration.h>

#include "SampleUtils.h"
#include "Texture.h"
#include "Teapot.h"

//use sora library
#include <sora.h>
#include "SRSceneNodeVisitor.h"
#include "SRScene.h"
#include "SRResource.h"
#include "SRLight.h"
#include "SRGLMachine.h"

//for gl emulator
using namespace sora::gles1;

#ifdef __cplusplus
extern "C"
{
#endif

// Textures:
int textureCount                = 0;
Texture** textures              = 0;

// Screen dimensions:
unsigned int screenWidth        = 0;
unsigned int screenHeight       = 0;

// Indicates whether screen is in portrait (true) or landscape (false) mode
bool isActivityInPortraitMode   = false;

// The projection matrix used for rendering virtual objects:
QCAR::Matrix44F projectionMatrix;

// Constants:
static const float kObjectScale	= 3.f;

//model1=삼각들어간거
//model2=사각형
bool model1Visible = true;
bool model2Visible = true;
void showModel1() { model1Visible = true; }
void hideModel1() { model1Visible = false; }
void showModel2() { model2Visible = true; }
void hideModel2() { model2Visible = false; }

JNIEXPORT void JNICALL
Java_ranka_sage_ARDelegate_showModel1(JNIEnv *, jobject)
{
	showModel1();
}
JNIEXPORT void JNICALL
Java_ranka_sage_ARDelegate_hideModel1(JNIEnv *, jobject)
{
	hideModel1();
}
JNIEXPORT void JNICALL
Java_ranka_sage_ARDelegate_showModel2(JNIEnv *, jobject)
{
	showModel2();
}
JNIEXPORT void JNICALL
Java_ranka_sage_ARDelegate_hideModel2(JNIEnv *, jobject)
{
	hideModel2();
}

//인식된 마커에 대한 정보
//int + flag로 해서 쓴다. 외부에서는 적절히 짤라서 쓰기
int activeMarker = 0;
//마커 코드값 + 마커의 이름
#define MARKER_MODEL1_ZERO	0x01
#define MARKER_MODEL1_LEFT	0x02
#define MARKER_MODEL1_RIGHT	0x04
#define MARKER_MODEL2_ZERO	0x10
#define MARKER_MODEL2_LEFT	0x20
#define MARKER_MODEL2_RIGHT	0x40

#define MODEL1_ZERO		"model1_0"
#define MODEL1_LEFT		"model1_1"
#define MODEL1_RIGHT	"model1_2"
#define MODEL2_ZERO		"model2_0"
#define MODEL2_LEFT		"model2_1"
#define MODEL2_RIGHT	"model2_2"

JNIEXPORT int JNICALL
Java_ranka_sage_ARDelegate_getActiveMarker(JNIEnv*, jobject)
{
	return activeMarker;
}

//sample texture
//sora::SampleTexture *tex;

JNIEXPORT int JNICALL
Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargets_getOpenGlEsVersionNative(JNIEnv *, jobject)
{
	return 1;
}


JNIEXPORT void JNICALL
Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargets_setActivityPortraitMode(JNIEnv *, jobject, jboolean isPortrait)
{
	isActivityInPortraitMode = isPortrait;
}


JNIEXPORT void JNICALL
Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargets_onQCARInitializedNative(JNIEnv *, jobject)
{
	// Comment in to enable tracking of up to 2 targets simultaneously and
	// split the work over multiple frames:
	const int maxTarget = 2;
	QCAR::setHint(QCAR::HINT_MAX_SIMULTANEOUS_IMAGE_TARGETS, maxTarget);
	QCAR::setHint(QCAR::HINT_IMAGE_TARGET_MULTI_FRAME_ENABLED, 1);
}


void drawModel1()
{
	//opengl 선 굵기 설정
	glLineWidth(3);

	glPushMatrix();
	float scale = 50;
	glScalef(scale, scale, scale);

	//wire frame으로 그리기
	glBegin(GL_LINES);

	float depth = -4;
	//정면, 뒷면의 5각형
	for(int i = 0 ; i < 2 ; i++)
	{
		float z = 0;
		if(i == 0)
		{
			//정면
			glColor4f(1, 0, 0, 1);
			z = 0;
		}
		else
		{
			//뒷면
			glColor4f(0, 1, 0, 1);
			z = depth;
		}
		//아랫쪽
		glVertex3f(-1, -1, z);
		glVertex3f(1, -1, z);
		//좌
		glVertex3f(-1, -1, z);
		glVertex3f(-1, 1, z);
		//우
		glVertex3f(1, -1, z);
		glVertex3f(1, 1, z);
		//위왼쪽
		glVertex3f(-1, 1, z);
		glVertex3f(0, 2, z);
		//위오른쪽
		glVertex3f(1, 1, z);
		glVertex3f(0, 2, z);
	}
	//정면과 뒷면을 연결하는선 5개
	glColor4f(0, 0, 1, 1);
	glVertex3f(-1, -1, 0);	glVertex3f(-1, -1, depth);
	glVertex3f(-1, 1, 0);	glVertex3f(-1, 1, depth);
	glVertex3f(0, 2, 0);	glVertex3f(0, 2, depth);
	glVertex3f(1, 1, 0);	glVertex3f(1, 1, depth);
	glVertex3f(1, -1, 0);	glVertex3f(1, -1, depth);

	glEnd();
	glPopMatrix();
}
void drawModel2()
{
	//opengl 선 굵기 설정
	glLineWidth(3);
	//sora::WireAxis axis(20);
	//sora::gles1::drawModel(&axis);

	glPushMatrix();

	float scale = 50;
	glScalef(scale, scale, scale);

	glBegin(GL_LINES);

	float depth = -1.5;
	for(int i = 0 ; i < 2 ; i++)
	{
		float z = 0;
		if(i == 0)
		{
			//앞면
			glColor4f(1, 0, 0, 1);
			z = 0;
		}
		else
		{
			//뒷면
			glColor4f(0, 1, 0, 1);
			z = depth;
		}
		//좌
		glVertex3f(-1, -1, z);
		glVertex3f(-1, 1, z);
		//위
		glVertex3f(-1, 1, z);
		glVertex3f(1, 1, z);
		//우
		glVertex3f(1, 1, z);
		glVertex3f(1, -1, z);
		//아래
		glVertex3f(1, -1, z);
		glVertex3f(-1, -1, z);
	}

	//옆면 선 그리기
	glColor4f(0, 0, 1, 1);
	glVertex3f(-1, -1, 0);	glVertex3f(-1, -1, depth);
	glVertex3f(-1, 1, 0);	glVertex3f(-1, 1, depth);
	glVertex3f(1, 1, 0);	glVertex3f(1, 1, depth);
	glVertex3f(1, -1, 0);	glVertex3f(1, -1, depth);

	glEnd();
	glPopMatrix();
}

JNIEXPORT void JNICALL
Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargetsRenderer_renderFrame(JNIEnv *, jobject)
{
	//LOG("Java_com_qualcomm_QCARSamples_ImageTargets_GLRenderer_renderFrame");

	// Clear color and depth buffer
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Render video background:
	QCAR::State state = QCAR::Renderer::getInstance().begin();

	// Set GL11 flags:
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	//glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	glEnable(GL_TEXTURE_2D);
	glDisable(GL_LIGHTING);
	//glDisable(GL_TEXTURE_2D);


	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);

	//trackables로 등록된것중에서 인식된것이 뭐가 있는가?
	//새로 얻은것도 갱신하기
	activeMarker = 0;
	for(int tIdx = 0; tIdx < state.getNumActiveTrackables(); tIdx++)
	{
		const QCAR::Trackable* trackable = state.getActiveTrackable(tIdx);
		if(strcmp(trackable->getName(), MODEL1_ZERO) == 0)
			activeMarker |= MARKER_MODEL1_ZERO;
		else if(strcmp(trackable->getName(), MODEL1_LEFT) == 0)
			activeMarker |= MARKER_MODEL1_LEFT;
		else if(strcmp(trackable->getName(), MODEL1_RIGHT) == 0)
			activeMarker |= MARKER_MODEL1_RIGHT;
		else if(strcmp(trackable->getName(), MODEL2_ZERO) == 0)
			activeMarker |= MARKER_MODEL2_ZERO;
		else if(strcmp(trackable->getName(), MODEL2_LEFT) == 0)
			activeMarker |= MARKER_MODEL2_LEFT;
		else if(strcmp(trackable->getName(), MODEL2_RIGHT) == 0)
			activeMarker |= MARKER_MODEL2_RIGHT;
	}
	//LOG("Marker Code : %d", activeMarker);

	//귀찮은 관계로 텍스쳐는 고정
	const Texture* const thisTexture = textures[0];

	//실제로 그리기
	for(int tIdx = 0; tIdx < state.getNumActiveTrackables(); tIdx++)
	{
		// Get the trackable:
		const QCAR::Trackable* trackable = state.getActiveTrackable(tIdx);

		bool trackableModel1 = false;
		bool trackableModel2 = false;
		if(strcmp(trackable->getName(), MODEL1_ZERO) == 0)
			trackableModel1 = true;
		if(strcmp(trackable->getName(), MODEL2_ZERO) == 0)
			trackableModel2 = true;

		if(trackableModel1 == true || trackableModel2 == true)
		{
			QCAR::Matrix44F modelViewMatrix = QCAR::Tool::convertPose2GLMatrix(trackable->getPose());

			//wire frame그리는거니까 tex없다
			glDisable(GL_TEXTURE_2D);

			// Load projection matrix:
			glMatrixMode(GL_PROJECTION);
			glLoadMatrixf(projectionMatrix.data);

			// Load model view matrix:
			glMatrixMode(GL_MODELVIEW);

			glLoadMatrixf(modelViewMatrix.data);
			glTranslatef(0.f, 0.f, kObjectScale);
			glScalef(kObjectScale, kObjectScale, kObjectScale);

			// Draw object:
			glBindTexture(GL_TEXTURE_2D, thisTexture->mTextureID);

			if(model1Visible == true && trackableModel1 == true)
			{
				drawModel1();
			}
			else if(model2Visible == true && trackableModel2 == true)
			{
				drawModel2();
			}
		}
	}

#if 0
	// Did we find any trackables this frame?
	for(int tIdx = 0; tIdx < state.getNumActiveTrackables(); tIdx++)
	{
		// Get the trackable:
		const QCAR::Trackable* trackable = state.getActiveTrackable(tIdx);
		QCAR::Matrix44F modelViewMatrix =
				QCAR::Tool::convertPose2GLMatrix(trackable->getPose());

		// Choose the texture based on the target name:
		int textureIndex = (!strcmp(trackable->getName(), "stones")) ? 0 : 1;
		const Texture* const thisTexture = textures[textureIndex];

#ifdef USE_OPENGL_ES_1_1
		// Load projection matrix:
		glMatrixMode(GL_PROJECTION);
		glLoadMatrixf(projectionMatrix.data);

		// Load model view matrix:
		glMatrixMode(GL_MODELVIEW);
		//glLoadIdentity();
		//glTranslatef(0, 0, -5);

		glLoadMatrixf(modelViewMatrix.data);
		glTranslatef(0.f, 0.f, kObjectScale);
		glScalef(kObjectScale, kObjectScale, kObjectScale);

		// Draw object:
		glBindTexture(GL_TEXTURE_2D, thisTexture->mTextureID);

		/*
		//add scene graph
		sora::SceneNodeGroup root;
		sora::SceneNodeGroup *group1 = new sora::SceneNodeGroup();
		root.addChild(group1);
		sora::ModelPtr m3 = sora::ModelLoader().loader().loadWireAxis(40);
		group1->addChild(new sora::ModelNode(m3));
		*/
		/*
		//그냥 그리면 깊이감이 너무 없다(왜냐하면 z=-인부분을 그리면 안보이는게 아니라 멀리보이는것처럼되니까)
		//이를 막기위해 xy평면을 만들었다(이것이 적절히 뒤로 가는것은 가려줄것이라 믿는다
		glDisable(GL_TEXTURE_2D);
		glPushMatrix();
		//zfighting 해결법 = z를 약간 야매로 주기
		float zvalue = -0.1;
		glScalef(30, 30, 30);
		sora::gles1::glBegin(SORA_GL_QUADS);
		glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
		sora::gles1::glVertex3f(1, 1, zvalue);
		sora::gles1::glVertex3f(-1, 1, zvalue);
		sora::gles1::glVertex3f(-1, -1, zvalue);
		sora::gles1::glVertex3f(1, -1, zvalue);
		sora::gles1::glEnd();
		glPopMatrix();
		
		glEnable(GL_TEXTURE_2D);
		sora::SceneNodeGroup root;
		//set transform
		//sora::Transformation t1 = sora::Transformation::translate(0, 0, -5);
		//root.transformation(t1);
		sora::Transformation t5 = sora::Transformation::scale(20);
		root.transformation(t5);

		sora::SceneNodeGroup *group1 = new sora::SceneNodeGroup();
		sora::SceneNodeGroup *group2 = new sora::SceneNodeGroup();
		root.addChild(group1);
		root.addChild(group2);
		
		{
			//group1을 회전
			static GLfloat xrot;
			static GLfloat yrot;
			static GLfloat zrot;

			xrot+=0.3f;
			yrot+=0.2f;
			zrot+=0.4f;

			sora::Transformation t2 = sora::Transformation::rotateX(xrot);
			group1->transformation(t2);
			sora::Transformation t3 = sora::Transformation::rotateY(yrot);
			group1->transformation(t3);
			sora::Transformation t4 = sora::Transformation::rotateZ(zrot);
			group1->transformation(t4);
		}
		
		//create model 
		matsu::vec4 color(0.5, 0, 0.5, 1);
		sora::ModelPtr m1 = sora::ModelLoader().loader().loadSolidTeapot(2, color);
		group1->addChild(new sora::ModelNode(m1));

		//bounding box
		sora::ModelPtr m2 = sora::ModelLoader().loader().loadBoundingBox(m1);
		group1->addChild(new sora::ModelNode(m2));

		//axis
		sora::ModelPtr m3 = sora::ModelLoader().loader().loadWireAxis(3);
		group2->addChild(new sora::ModelNode(m3));
	
		//render
		sora::GLES1Renderer renderer;
		renderer.render(&root);
		*/
		

		//opengl 선 굵기 설정
		//glLineWidth(3);
		//sora::WireAxis axis(40);
		//sora::gles1::drawModel(&axis);

		/*
		glTexCoordPointer(2, GL_FLOAT, 0, (const GLvoid*) &teapotTexCoords[0]);
		glVertexPointer(3, GL_FLOAT, 0, (const GLvoid*) &teapotVertices[0]);
		glNormalPointer(GL_FLOAT, 0,  (const GLvoid*) &teapotNormals[0]);
		glDrawElements(GL_TRIANGLES, NUM_TEAPOT_OBJECT_INDEX, GL_UNSIGNED_SHORT,
				(const GLvoid*) &teapotIndices[0]);
				*/
#endif

	}
#endif

	glDisable(GL_DEPTH_TEST);

#ifdef USE_OPENGL_ES_1_1        
	glDisable(GL_TEXTURE_2D);
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
#endif

	QCAR::Renderer::getInstance().end();
}



void
configureVideoBackground()
{
	// Get the default video mode:
	QCAR::CameraDevice& cameraDevice = QCAR::CameraDevice::getInstance();
	QCAR::VideoMode videoMode = cameraDevice.
			getVideoMode(QCAR::CameraDevice::MODE_DEFAULT);

	// Configure the video background
	QCAR::VideoBackgroundConfig config;
	config.mEnabled = true;
	config.mSynchronous = true;
	config.mPosition.data[0] = 0.0f;
	config.mPosition.data[1] = 0.0f;

	if (isActivityInPortraitMode)
	{
		//LOG("configureVideoBackground PORTRAIT");
		config.mSize.data[0] = videoMode.mHeight
				* (screenHeight / (float)videoMode.mWidth);
		config.mSize.data[1] = screenHeight;
	}
	else
	{
		//LOG("configureVideoBackground LANDSCAPE");
		config.mSize.data[0] = screenWidth;
		config.mSize.data[1] = videoMode.mHeight
				* (screenWidth / (float)videoMode.mWidth);
	}

	// Set the config:
	QCAR::Renderer::getInstance().setVideoBackgroundConfig(config);
}


JNIEXPORT void JNICALL
Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargets_initApplicationNative(
		JNIEnv* env, jobject obj, jint width, jint height)
{
	LOG("Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargets_initApplicationNative");

	// Store screen dimensions
	screenWidth = width;
	screenHeight = height;

	// Handle to the activity class:
	jclass activityClass = env->GetObjectClass(obj);

	jmethodID getTextureCountMethodID = env->GetMethodID(activityClass,
			"getTextureCount", "()I");
	if (getTextureCountMethodID == 0)
	{
		LOG("Function getTextureCount() not found.");
		return;
	}

	textureCount = (int) env->CallObjectMethod(obj, getTextureCountMethodID);
	if (!textureCount)
	{
		LOG("getTextureCount() returned zero.");
		return;
	}

	textures = new Texture*[textureCount];

	jmethodID getTextureMethodID = env->GetMethodID(activityClass,
			"getTexture", "(I)Lcom/qualcomm/QCARSamples/ImageTargets/Texture;");

	if (getTextureMethodID == 0)
	{
		LOG("Function getTexture() not found.");
		return;
	}

	// Register the textures
	for (int i = 0; i < textureCount; ++i)
	{

		jobject textureObject = env->CallObjectMethod(obj, getTextureMethodID, i);
		if (textureObject == NULL)
		{
			LOG("GetTexture() returned zero pointer");
			return;
		}

		textures[i] = Texture::create(env, textureObject);
	}
}


JNIEXPORT void JNICALL
Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargets_deinitApplicationNative(
		JNIEnv* env, jobject obj)
{
	LOG("Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargets_deinitApplicationNative");

	// Release texture resources
	if (textures != 0)
	{
		for (int i = 0; i < textureCount; ++i)
		{
			delete textures[i];
			textures[i] = NULL;
		}

		delete[]textures;
		textures = NULL;

		textureCount = 0;
	}
}


JNIEXPORT void JNICALL
Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargets_startCamera(JNIEnv *,
		jobject)
{
	LOG("Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargets_startCamera");

	// Initialize the camera:
	if (!QCAR::CameraDevice::getInstance().init())
		return;

	// Configure the video background
	configureVideoBackground();

	// Select the default mode:
	if (!QCAR::CameraDevice::getInstance().selectVideoMode(
			QCAR::CameraDevice::MODE_DEFAULT))
		return;

	// Start the camera:
	if (!QCAR::CameraDevice::getInstance().start())
		return;

	// Start the tracker:
	QCAR::Tracker::getInstance().start();

	// Cache the projection matrix:
	const QCAR::Tracker& tracker = QCAR::Tracker::getInstance();
	const QCAR::CameraCalibration& cameraCalibration =
			tracker.getCameraCalibration();
	projectionMatrix = QCAR::Tool::getProjectionGL(cameraCalibration, 2.0f,
			2000.0f);
}


JNIEXPORT void JNICALL
Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargets_stopCamera(JNIEnv *,
		jobject)
{
	LOG("Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargets_stopCamera");

	QCAR::Tracker::getInstance().stop();

	QCAR::CameraDevice::getInstance().stop();
	QCAR::CameraDevice::getInstance().deinit();
}


JNIEXPORT void JNICALL
Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargetsRenderer_initRendering(
		JNIEnv* env, jobject obj)
{
	LOG("Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargetsRenderer_initRendering");

	// Define clear color
	glClearColor(0.0f, 0.0f, 0.0f, QCAR::requiresAlpha() ? 0.0f : 1.0f);

	// Now generate the OpenGL texture objects and add settings
	for (int i = 0; i < textureCount; ++i)
	{
		glGenTextures(1, &(textures[i]->mTextureID));
		glBindTexture(GL_TEXTURE_2D, textures[i]->mTextureID);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textures[i]->mWidth,
				textures[i]->mHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
				(GLvoid*)  textures[i]->mData);
	}
	
	//sora용 texture불러오기
	//tex = new sora::SampleTexture();
}


JNIEXPORT void JNICALL
Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargetsRenderer_updateRendering(
		JNIEnv* env, jobject obj, jint width, jint height)
{
	LOG("Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargetsRenderer_updateRendering");

	// Update screen dimensions
	screenWidth = width;
	screenHeight = height;

	// Reconfigure the video background
	configureVideoBackground();
}


#ifdef __cplusplus
}
#endif
